﻿// <copyright file="VirtualRegistryEntry.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// This class represents a virtual registry entry in a CP file.
    /// </summary>
    public class VirtualRegistryEntry
    {
        /// <summary>
        /// The size of the entry block in total number of bytes.
        /// </summary>
        /// <remarks>
        /// If this value is 0x00000000, the end of the section has been reached and parsing must be stopped.
        /// </remarks>
        private int entrySize;

        /// <summary>
        /// The size of the entry block header (i.e., all the data before key/value names and value data).
        /// </summary>
        /// <remarks>
        /// This field has the value 0x00000030 always.
        /// </remarks>
        private int entryBlockHeaderSize;
        
        /// <summary>
        /// The type of the registry entry.
        /// </summary>
        private RegistryEntryType entryType;
        
        /// <summary>
        /// The relative byte offset to the key or value name string inside entry block.
        /// </summary>
        /// <remarks>
        /// The offset is relative to the beginning of the entry block header.
        /// </remarks>
        private int nameOffset;
        
        /// <summary>
        /// The size of the key or value name string in bytes.
        /// </summary>
        private int nameSize;
        
        /// <summary>
        /// The relative byte offset to the value data inside entry block.
        /// </summary>
        /// <remarks>
        /// The offset is relative to the beginning of the entry block header. This field is used with registry
        /// values and keys having default value; otherwise, the field is initialized to 0x00000000 on key entries.
        /// </remarks>
        private int dataOffset;
        
        /// <summary>
        /// The size of the value data in bytes.
        /// </summary>
        /// <remarks>
        /// This field is used with registry values and keys having default value; otherwise, the field is
        /// initialized to 0x00000000 on key entries.
        /// </remarks>
        private int dataLength;
        
        /// <summary>
        /// Indicates the attributes of the virtual registry value.
        /// </summary>
        private RegistryEntryAttributes attributes;
        
        /// <summary>
        /// The last written timestamp for the registry entry.
        /// </summary>
        /// <remarks>
        /// In the SFT file, this value is stored as Win32 FILETIME structure.
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        private DateTime lastWriteTimestamp;

        /// <summary>
        /// The type of the registry value.
        /// </summary>
        /// <remarks>
        /// See the Microsoft Windows SDK for a complete listing of registry value types.
        /// </remarks>
        private int valueType;

        // TODO: Maybe the virtualize field should be Boolean, with translation when reading and writing the SFT file.

        /// <summary>
        /// This field has the value 1 if this registry entry is to be virtualized and 0 if it is not a virtualized entry.
        /// </summary>        
        private int virtualize;
        
        /// <summary>
        /// Indicates the type of registry entry.
        /// </summary>
        private VirtualRegistryEntryType flags;
        
        /// <summary>
        /// The name of the registry entry.
        /// </summary>
        private string name;

        /// <summary>
        /// The value contained in the registry entry, provided the entry is not a key.
        /// </summary>
        private VirtualRegistryEntryValue valueData;

        private List<VirtualRegistryEntry> childValues;

        /// <summary>
        /// Initializes a new instance of the VirtualRegistryEntry class.
        /// </summary>
        /// <param name="dataStream">
        /// A memory stream containing the raw virtual registry entry data from the CP file.
        /// </param>
        public VirtualRegistryEntry(System.IO.MemoryStream dataStream)
        {
            this.childValues = null;
            this.ReadSection(dataStream);
        }

        /// <summary>
        /// Gets the attributes of the virtual registry value.
        /// </summary>
        public RegistryEntryAttributes Attributes
        {
            get { return this.attributes; }
        }
        
        /// <summary>
        /// Gets the last written timestamp for the registry entry.
        /// </summary>
        /// <remarks>
        /// In the SFT file, this value is stored as Win32 FILETIME structure.
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        public DateTime LastWrittenTimestamp
        {
            get { return this.lastWriteTimestamp; }
        }

        /// <summary>
        /// Gets a value indicating whether this registry entry is to be virtualized.
        /// </summary>        
        public bool Virtualize
        {
            get { return this.virtualize == 1; }
        }
        
        /// <summary>
        /// Gets the type of registry entry.
        /// </summary>
        public VirtualRegistryEntryType Flags
        {
            get { return this.flags; }
        }

        /// <summary>
        /// Gets the size of the entry block in total number of bytes.
        /// </summary>
        /// <remarks>
        /// If this value is 0x00000000, the end of the section has been reached and parsing must be stopped.
        /// </remarks>
        public int EntrySize
        {
            get { return this.entrySize; }
        }

        /// <summary>
        /// Gets the name of the registry entry.
        /// </summary>
        public string Name
        {
            get { return this.name; }
        }

        /// <summary>
        /// Gets a value indicating the type of the registry entry.
        /// </summary>
        public RegistryEntryType EntryType
        {
            get { return this.entryType; }
        }

        /// <summary>
        /// Gets a value indicating the data type of the registry value.
        /// </summary>
        public Microsoft.Win32.RegistryValueKind ValueType
        {
            get
            {
                if (System.Enum.IsDefined(typeof(Microsoft.Win32.RegistryValueKind), this.valueType))
                {
                    return (Microsoft.Win32.RegistryValueKind)this.valueType;
                }
                else
                {
                    return Microsoft.Win32.RegistryValueKind.Unknown;
                }
            }
        }

        // This is not part of the specification.
        public string ValueTypeAsWindowsString
        {
            get
            {
                switch (this.ValueType)
                {
                    case Microsoft.Win32.RegistryValueKind.Binary:
                        return "REG_BINARY";
                    case Microsoft.Win32.RegistryValueKind.DWord:
                        return "REG_DWORD";
                    case Microsoft.Win32.RegistryValueKind.ExpandString:
                        return "REG_EXPAND_SZ";
                    case Microsoft.Win32.RegistryValueKind.MultiString:
                        return "REG_MULTI_SZ";
                    case Microsoft.Win32.RegistryValueKind.QWord:
                        return "REG_QWORD";
                    case Microsoft.Win32.RegistryValueKind.String:
                        return "REG_SZ";
                    default:
                        return "Unknown";
                }
            }
        }

        /// <summary>
        /// Gets the value for the registry entry, provided the entry is not a key.
        /// </summary>
        public VirtualRegistryEntryValue Value
        {
            get { return this.valueData; }
        }

        public List<VirtualRegistryEntry> ChildEntries
        {
            get
            {
                if (this.entryType == RegistryEntryType.Value)
                {
                    return null;
                }
                else
                {
                    return this.childValues;
                }
            }
        }

        /// <summary>
        /// Reads the actual data for this section from the file.
        /// </summary>
        /// <param name="dataStream">
        /// A MemoryStream containing the raw data for the virtual registry entry.
        /// </param>
        private void ReadSection(System.IO.MemoryStream dataStream)
        {
            System.IO.BinaryReader streamReader = new System.IO.BinaryReader(dataStream);

            this.entrySize = streamReader.ReadInt32();
            if (this.entrySize > 0)
            {
                this.entryBlockHeaderSize = streamReader.ReadInt32();
                this.entryType = (RegistryEntryType)streamReader.ReadInt32();
                this.childValues = (this.entryType == RegistryEntryType.Key) ? new List<VirtualRegistryEntry>() : null;
                this.nameOffset = streamReader.ReadInt32();
                this.nameSize = streamReader.ReadInt32();
                this.dataOffset = streamReader.ReadInt32();
                this.dataLength = streamReader.ReadInt32();
                this.attributes = (RegistryEntryAttributes)streamReader.ReadInt32();
                this.lastWriteTimestamp = DateTime.FromFileTimeUtc(streamReader.ReadInt64());
                this.valueType = streamReader.ReadInt32();
                this.virtualize = streamReader.ReadInt32();
                this.flags = (VirtualRegistryEntryType)streamReader.ReadInt32();
                this.name = System.Text.Encoding.Unicode.GetString(streamReader.ReadBytes(this.nameSize));

                byte[] rawDataValue = streamReader.ReadBytes(this.dataLength);
                if ((this.attributes & RegistryEntryAttributes.HasValue) == RegistryEntryAttributes.HasValue)
                {
                    switch (this.ValueType)
                    {
                        case Microsoft.Win32.RegistryValueKind.String:
                        case Microsoft.Win32.RegistryValueKind.MultiString:
                        case Microsoft.Win32.RegistryValueKind.ExpandString:
                            this.valueData = new VirtualRegistryEntryStringValue(rawDataValue);
                            break;
                        case Microsoft.Win32.RegistryValueKind.Binary:
                            this.valueData = new VirtualRegistryEntryBinaryValue(rawDataValue);
                            break;
                        case Microsoft.Win32.RegistryValueKind.DWord:
                            this.valueData = new VirtualRegistryEntryDWordValue(rawDataValue);
                            break;
                    }
                }
            }
        }
    }
}
